package com.kunpan.admin.aop;

import cn.dev33.satoken.stp.StpUtil;
import com.kunpan.admin.annotation.AutoFill;
import com.kunpan.admin.async.AsyncService;
import com.kunpan.admin.common.Enum.BusinessType;
import com.kunpan.admin.common.domain.AjaxResult;
import com.kunpan.admin.common.redis.RedisMessagePublisher;
import com.kunpan.admin.constant.Constants;
import com.kunpan.admin.entity.User;
import com.kunpan.admin.entity.dto.FileShareDto;
import com.kunpan.admin.exception.file.InsufficientSpaceException;
import com.kunpan.admin.exception.file.ValidityException;
import com.kunpan.admin.mapper.UserMapper;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;


//@Slf4j
@Aspect
@Component
public class AutoFillAspect {

    private static final Logger log = LoggerFactory.getLogger(AutoFillAspect.class);

    @Autowired
    private AsyncService asyncService;

    @Autowired
    private RedisMessagePublisher redisMessagePublisher;


    @Autowired
    private UserMapper userMapper;

    // 定义切面，在保存方法执行前自动填充字段
//    *：表示任意返回类型，即不限制方法的返回类型。
//    com.blog.*.controller：表示com.blog包下的所有子包中的controller包。*表示该包的任何子包，例如 com.blog.article.controller 或 com.blog.user.controller。
//    *：表示controller包下的任意类中的任意方法。
//    (..)：表示方法参数为任意类型和数量
//    @Before("execution(* com.blog.*.controller.*(..))")
//    public void beforeSave(JoinPoint joinPoint) {
//        for (Object arg : joinPoint.getArgs()) {
//            autoFillFields(arg, true);
//        }
//    }

//    JoinPoint：当你只需要获取目标方法的信息（如参数、方法签名等），而不需要改变目标方法的执行或返回值时，
//    使用 JoinPoint
//    即可。
//    ProceedingJoinPoint：当你需要手动控制目标方法的执行（比如：在特定条件下不执行目标方法、修改目标方法的返回值等）时，
//    必须使用 ProceedingJoinPoint

    /**
     * 定义切点,表示具体执行的点，也就是什么时候执行通知的点
     */
    @Pointcut("execution(* com.kunpan.*.controller.*.*(..)) && @annotation(com.kunpan.admin.annotation.AutoFill)")
    public void autoFillPointCut() {
    }

    /**
     * 给文件分享控制器增加时间字段
     */
    @Pointcut("execution(* com.kunpan.*.controller.FIleShareController.addFileShare(..))")
    public void fileSharePointCut() {
    }

    @Pointcut("execution(* com.kunpan.*.controller.PanFileController.*(..)) && @annotation(com.kunpan.admin.annotation.AutoFill)")
    public void checkSpacePointCut() {
    }

    // 定义切面，在更新方法执行前自动填充字段
    @Before("autoFillPointCut()")
    public void beforeSet(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        // 获取方法上的 @AutoFill 注解
        AutoFill autoFill = method.getAnnotation(AutoFill.class);

        //2.1 获取参数
        Object[] args = joinPoint.getArgs();
        if (args == null || args.length == 0) {
            return;
        }
        //2.2 获取第一个参数，可能是实体类对象也有可能是其他对象
        Object entity = args[0];
        log.info("获取到登入对象: {}", entity);
        // 获取操作类型
        BusinessType businessType = autoFill.value();
        //创建时间
        Date now = new Date();
        try {
            if (businessType == BusinessType.INSERT || businessType == BusinessType.NEWFOLD) {
                // 填充创建时间和更新时间,通过反射的方式拿到实体类的set方法，当前为setCreateTime()和setUpdateTime()
                Method setCreateTime = entity.getClass().getMethod(Constants.SET_CREATE_TIME, Date.class);
                Method setUpdateTime = entity.getClass().getMethod(Constants.SET_UPDATE_TIME, Date.class);
                setCreateTime.invoke(entity, now);
                setUpdateTime.invoke(entity, now);
//                Integer userId = Integer.valueOf(StpUtil.getLoginId().toString());
//                String cacheKey1 = String.format(Constants.FILE_TYPE_LIST_CACHE_KEY, userId,null);
//                String cacheKey2 = String.format(Constants.FILE_TYPE_LIST_CACHE_KEY, userId ,0);
//                //发布清除对应缓存的消息
//                redisMessagePublisher.publishFileUploadEvent(cacheKey1);
//                redisMessagePublisher.publishFileUploadEvent(cacheKey2);
            } else if (businessType == BusinessType.UPDATE) {
                // 填充更新时间
                Method setUpdateTime = entity.getClass().getMethod(Constants.SET_UPDATE_TIME, Date.class);
                setUpdateTime.invoke(entity, now);
            } else {
                return;
            }
        } catch (Exception e) {
            log.error("自动填充字段失败: {}", e);
        }
    }


    //登入操作成功之后执行的方法，returning = "result"是拿到此操作的返回值
    @AfterReturning(value = "autoFillPointCut()", returning = "result")
    public void afterSet(JoinPoint joinPoint, Object result) {
        if (result instanceof AjaxResult) {
            AjaxResult ajaxResult = (AjaxResult) result;
            if ((int) ajaxResult.get("code") == 200) {
                // 获取方法上的 @AutoFill 注解
                MethodSignature signature = (MethodSignature) joinPoint.getSignature();
                Method method = signature.getMethod();
                AutoFill autoFill = method.getAnnotation(AutoFill.class);

                //2.1 获取参数
                Object[] args = joinPoint.getArgs();
                if (args == null || args.length == 0) {
                    return;
                }
                //2.2 获取第一个参数，可能是实体类对象也有可能是其他对象
                Object entity = args[0];
                log.info("获取到的对象: {}", entity);
                // 获取操作类型
                BusinessType businessType = autoFill.value();
                //创建时间
                Date now = new Date();
                try {
                    if (businessType == BusinessType.RECORD) {
                        // 填充创建时间和更新时间,通过反射的方式拿到实体类的set方法，当前为setCreateTime()和setUpdateTime()
                        Class objClass = entity.getClass();
                        Field[] fields = objClass.getDeclaredFields();
                        for (Field field : fields) {
                            if (field.getName().equals("email")) {
                                field.setAccessible(true);
                                Object value = field.get(entity);
                                if (value != null) {
                                    String email = (String) value;
                                    log.info("获取到email: {}", email);
                                    // 异步操作，根据 email 更新最后登录时间
                                    asyncService.recordLoginTime(email, now);
                                } else {
                                    log.warn("email字段为空");
                                }
                            }
                        }
                    } else {
                        return;
                    }
                } catch (Exception e) {
                    log.error("获取email失败: {}", e);
                }
            } else {
                log.info("登入失败，不进行记录时间操作");
            }
        }
    }


    @Before("checkSpacePointCut()")
    public void checkAvailableSpace(JoinPoint joinPoint) {
        log.info("开始执行判断空间是否充足操作");
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        AutoFill autoFill = method.getAnnotation(AutoFill.class);
        //2.1 获取参数
        Object[] args = joinPoint.getArgs();
        if (args == null || args.length == 0) {
            return;
        }
        Long size;
        //2.2 获取第一个参数，可能是实体类对象也有可能是其他对象
        Object entity = args[0];
        if (autoFill.value().equals(BusinessType.NORMALUPLOAD)) {
            MultipartFile file = (MultipartFile) entity;
            size = file.getSize();
        } else if (autoFill.value().equals(BusinessType.FASTUPLOAD)) {//判断是否是秒传
            size = (Long) entity;
        } else if (autoFill.value().equals(BusinessType.FRAGMENTUPLOAD)) {
            if ((Integer) args[1] > 0) {//判断当前分片是不是第一次上传
                return;
            }
            entity = args[4];
            size = (Long) entity;
        } else {
            return;
        }
        log.info("获取到的文件大小为: {}", size);
        //查询当前用户的剩余空间
        Integer userId = Integer.valueOf(StpUtil.getLoginId().toString());
        User user = userMapper.selectUserById(userId);
        if (user != null) {
            Long freeSpace = user.getTotalSpace() - user.getUseSpace();
            if (freeSpace < size) {//剩余空间小于文件大小，返回上传失败提示
                throw new InsufficientSpaceException("上传失败，剩余空间不足");
            }
        }
    }


    //@Before: 前置通知
    @Before("fileSharePointCut()")
    public void FileShareDateSet(JoinPoint joinPoint) {
        //2.1 获取参数
        Object[] args = joinPoint.getArgs();
        if (args == null || args.length == 0) {
            return;
        }

        //2.2 获取第一个参数，可能是实体类对象也有可能是其他对象
        Object entity = args[0];
        FileShareDto fileShareDto = (FileShareDto) entity;
        log.info("文件分享操作获取到对象: {}", entity);
        //创建时间
        Date now = new Date();
        //获取有效时间根据索引计算有效时间
        int[] arr = {-1, 1, 7, 30, 0};
        try {
            // 填充过期时间和分享时间,通过反射的方式拿到实体类的set方法
            Method setShareTime = entity.getClass().getMethod(Constants.SET_SHARE_TIME, Date.class);
            Method setExpireTime = entity.getClass().getMethod(Constants.SET_EXPIRE_TIME, Date.class);
            setShareTime.invoke(entity, now);
            int day = arr[fileShareDto.getValidType()];
            if (day == -1) {
                throw new ValidityException("无效时间");
            } else if (day == 0) {
                setExpireTime.invoke(entity, (Object) null);
            } else {
                // 将 Date 转换为 LocalDateTime
                LocalDateTime localDateTime = now.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
                // 计算保留的天数
                LocalDateTime sevenDaysLaterDateTime = localDateTime.plusDays(day);
                Date expireTime = Date.from(sevenDaysLaterDateTime.atZone(ZoneId.systemDefault()).toInstant());
                setExpireTime.invoke(entity, expireTime);
            }
        } catch (Exception e) {
            log.error("文件分享操作自动填充字段失败: {}", e.getMessage());
            throw new RuntimeException("文件分享操作自动填充字段失败", e);
        }
    }
}
